package com.google.code.jholidays.events;

import java.util.Calendar;
import java.util.Date;

import com.google.code.jholidays.core.EventDescriptor;
import com.google.code.jholidays.events.parameters.DateParameters;
import com.google.code.jholidays.events.parameters.DayOfWeekOrder;
import com.google.code.jholidays.events.properties.FloatingEventProperties;

/**
 * Represents floating event.
 * <p>
 * Floating event's date depends on year. For example when we say
 * "this event occurs on the first Sunday of July of each year" we are talking
 * about floating event. Those events has three major aspects:
 * <p>
 * 1. <b>Order</b> in month (e.g. first, second, third, fourth and <i>last</i>)<br>
 * 2. <b>Day</b> of week (Sun,Mon,Tue...)<br>
 * 3. <b>Month</b> (Jan,Feb,Mar...)<br>
 * <p>
 * Using order, day and month you can specify any floating event. Here are some
 * more examples of floating events:
 * <p>
 * First monday of september (<tt>order</tt>=first,<tt>day of week</tt>=Mon,
 * <tt>month</tt>=Sep)<br>
 * Second sunday of May (<tt>order</tt>=second,<tt>day of week</tt>=Sun,
 * <tt>month</tt>=May)<br>
 * Third friday of december (<tt>order</tt>=third,<tt>day of week</tt>=Fri,
 * <tt>month</tt>=Dec)<br>
 * Last tuesday of April(<tt>order</tt>=last,<tt>day of week</tt>=Tue,
 * <tt>month</tt>=Apr)<br>
 * <p>
 * Floating events do not have any parents.
 * 
 * @author tillias
 * @see AbstractEvent
 * @see EventDescriptor
 * @see FloatingEventProperties
 * 
 */
public class FloatingEvent extends AbstractEvent {

    /**
     * Creates new instance of floating event using given properties
     * 
     * @param properties
     *            Event properties
     * @throws IllegalArgumentException
     *             Thrown if properties parameter passed to constructor is null
     */
    protected FloatingEvent(FloatingEventProperties properties)
	    throws IllegalArgumentException {
	super(properties);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Date getDate(int year) {
	Date result = null;

	try {
	    Calendar calendar = Calendar.getInstance();
	    calendar.clear();

	    DateParameters params = getDateParameters();
	    if (params != null) {

		int orderIndex = getOrderIndex(year, params.getMonth(), params
			.getDayOfWeek(), params.getOrder());

		if (orderIndex != -1) {
		    calendar.set(year, params.getMonth(), 1);
		    calendar.set(Calendar.DAY_OF_WEEK, params.getDayOfWeek());
		    calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, orderIndex);

		    result = calendar.getTime();
		}
	    }
	} catch (Exception e) {
	    result = null;
	}

	return result;
    }

    protected DateParameters getDateParameters() {
	return ((FloatingEventProperties) getProperties()).getDateParameters();
    }

    private int getOrderIndex(int year, int month, int dayOfWeek,
	    DayOfWeekOrder order) {
	int result = -1;

	switch (order) {
	case First:
	    result = 1;
	    break;
	case Second:
	    result = 2;
	    break;
	case Third:
	    result = 3;
	    break;
	case Fourth:
	    result = 4;
	    break;
	case Last:
	    result = getLastOrderInMonth(year, month, dayOfWeek);
	    break;
	default:
	    break;
	}

	return result;
    }

    private int getLastOrderInMonth(int year, int month, int dayOfWeek) {
	int result = -1;

	try {
	    Calendar c = Calendar.getInstance();
	    c.clear();
	    c.set(year, month, 1);
	    c.set(Calendar.DAY_OF_WEEK, dayOfWeek);

	    result = c.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH);
	} catch (Exception e) {
	    // nothing todo here
	}

	return result;
    }

}
